home *** CD-ROM | disk | FTP | other *** search
- unit UCAniIcon;
-
- interface
-
- uses Windows, SysUtils, Consts, Classes, Graphics;
-
- type
- TAniIconHeader = record
- dwSizeof: LongInt;
- dwFrames: LongInt;
- dwSteps: LongInt;
- dwCX: LongInt; { use this to store icon width }
- dwCY: LongInt; { use this to store icon height }
- dwBitCount: LongInt;
- dwPlanes: LongInt;
- dwJIFRate: LongInt;
- dwFlags: LongInt;
- end;
-
- TAniIcon = class (TGraphic)
- private
- Rates: TList; { Optional JIFRate info for each step }
- FrameOffsets: TList; { Stream offsets into each frame }
- SequenceMap: TList; { Optional frame sequence mapping }
- Image: TMemoryStream; { Memory Image of entire .ANI file }
- fAuthor: String; { Optional author information }
- fTitle: String; { Optional title information }
- fHeader: TAniIconHeader; { ANI header extracted from file }
- fCurrentJIFs: Integer; { current JIF count for this step }
- fCurrentStep: Integer; { current step number }
- fCurrentFrame: Integer; { currently displaying frame number }
- fCurrentIcon: hIcon; { currently displaying icon }
- fTransparent: Boolean; { for transparent blitting }
- fBackColor: TColor; { background color when not transparent }
- procedure SetFrame (Index: Integer);
- public
- procedure Clear;
- constructor Create; override;
- destructor Destroy; override;
- procedure Assign (Source: TPersistent); override;
- procedure LoadFromStream (Stream: TStream); override;
- procedure SaveToStream (Stream: TStream); override;
- procedure Animate;
- procedure LoadFromClipboardFormat (AFormat: Word; AData: THandle; APalette: HPalette); override;
- procedure SaveToClipboardFormat (var Format: Word; var Data: THandle; var APalette: HPalette); override;
- procedure Draw (ACanvas: TCanvas; const Rect: TRect); override;
- procedure SetAnimatedCursor (Index: Integer);
- property Author: String read fAuthor;
- property Title: String read fTitle;
- property Icon: hIcon read fCurrentIcon;
- property Transparent: Boolean read fTransparent write fTransparent default False;
- property BackgroundColor: TColor read fBackColor write fBackColor default clBtnFace;
- protected
- function GetEmpty: Boolean; override;
- function GetHeight: Integer; override;
- function GetWidth: Integer; override;
- procedure SetHeight (Value: Integer); override;
- procedure SetWidth (Value: Integer); override;
- end;
-
- implementation
-
- { TAniIcon }
-
- uses Forms;
-
- constructor TAniIcon.Create;
- begin
- Inherited Create;
- fTransparent := False;
- fBackColor := clBtnFace;
- Rates := TList.Create;
- FrameOffsets := TList.Create;
- SequenceMap := TList.Create;
- Image := TMemoryStream.Create;
- end;
-
- destructor TAniIcon.Destroy;
- begin
- Clear;
- Image.Free;
- Rates.Free;
- FrameOffsets.Free;
- SequenceMap.Free;
- Inherited Destroy;
- end;
-
- procedure TAniIcon.Clear;
- begin
- fAuthor := '--unavailable--';
- fTitle := '--unavailable--';
- Image.Clear;
- Rates.Clear;
- FrameOffsets.Clear;
- SequenceMap.Clear;
- if fCurrentIcon <> 0 then DestroyIcon (fCurrentIcon);
- fCurrentIcon := 0;
- fCurrentJIFs := 0;
- fCurrentStep := 0;
- fCurrentFrame := 0;
- end;
-
- procedure TAniIcon.Assign (Source: TPersistent);
- begin
- if Source = Nil then Clear
- else if Source is TAniIcon then LoadFromStream (TAniIcon (Source).Image)
- else Inherited Assign (Source);
- end;
-
- function TAniIcon.GetEmpty: Boolean;
- begin
- Result := FrameOffsets.Count = 0;
- end;
-
- procedure TAniIcon.SetHeight (Value: Integer);
- begin
- raise EInvalidGraphicOperation.Create (sChangeIconSize);
- end;
-
- procedure TAniIcon.SetWidth (Value: Integer);
- begin
- raise EInvalidGraphicOperation.Create (sChangeIconSize);
- end;
-
- function TAniIcon.GetWidth: Integer;
- begin
- Result := fHeader.dwCX;
- end;
-
- function TAniIcon.GetHeight: Integer;
- begin
- Result := fHeader.dwCY;
- end;
-
- procedure TAniIcon.LoadFromClipboardFormat (AFormat: Word; AData: THandle; APalette: HPalette);
- begin
- raise EInvalidGraphicOperation.Create (sIconToClipboard);
- end;
-
- procedure TAniIcon.SaveToClipboardFormat (var Format: Word; var Data: THandle; var APalette: HPalette);
- begin
- raise EInvalidGraphicOperation.Create (sIconToClipboard);
- end;
-
- procedure TAniIcon.LoadFromStream (Stream: TStream);
- const
- sig_RIFF = $46464952; { RIFF header }
- sig_ACON = $4E4F4341; { ACON form type }
- sig_LIST = $5453494C; { LIST sub-chunk }
- sig_INFO = $4F464E49; { INFO sub-chunk }
- sig_INAM = $4D414E49; { INAM - title information }
- sig_IART = $54524149; { IART - author information }
- sig_anih = $68696E61; { anih - header information }
- sig_rate = $65746172; { optional JIF rates sub-chunk }
- sig_fram = $6D617266; { fram - list of icon frames }
- sig_icon = $6E6F6369; { icon - start of actual frame }
- sig_seq = $20716573; { seq - optional sequence information }
-
- var
- ChunkLen: LongInt;
- EncounteredHeader: Boolean;
-
- procedure InvalidFile;
- begin
- raise EInvalidGraphic.Create ('Animated icon image is not valid');
- end;
-
- function ReadByte: Byte;
- begin
- Image.ReadBuffer (Result, sizeof (Result));
- end;
-
- function ReadLong: LongInt;
- begin
- Image.ReadBuffer (Result, sizeof (Result));
- end;
-
- function ReadString: String;
- var
- p: PChar;
- Len: LongInt;
- begin
- Len := ReadLong;
- if (Len and 1) <> 0 then Inc (Len);
- GetMem (p, Len + 1);
- p[Len] := #0;
- Image.ReadBuffer (p^, Len);
- Result := StrPas (p);
- FreeMem (p, Len + 1);
- end;
-
- { Process an optional info header sub-chunk. Contains Title/Author }
- procedure ParseTitleAuthor;
- var
- ChunkEnd: LongInt;
- begin
- ChunkEnd := ReadLong;
- Inc (ChunkEnd, Image.Position);
- if ReadLong <> sig_INFO then InvalidFile;
-
- while Image.Position < ChunkEnd do
- case ReadLong of
- sig_INAM: fTitle := ReadString;
- sig_IART: fAuthor := ReadString;
- end;
- end;
-
- { Parse ANI header information }
- procedure ParseAniHeader;
- begin
- if ReadLong <> sizeof (fHeader) then InvalidFile;
- Image.ReadBuffer (fHeader, sizeof (fHeader));
- EncounteredHeader := True;
- end;
-
- { Parse optional JIFRates chunk OR }
- { optional Sequence Map }
- procedure ParseList (List: TList);
- var
- Len: LongInt;
- begin
- Len := ReadLong div sizeof (LongInt);
- if Len <> fHeader.dwSteps then InvalidFile;
- while Len > 0 do begin
- List.Add (Pointer (ReadLong));
- Dec (Len);
- end;
- end;
-
- { Parse the actual icon data itself }
- procedure ParseIconList;
- var
- Idx: Integer;
- Len, Next: LongInt;
- begin
- ReadLong; { Discard chunk length }
- if ReadLong <> sig_fram then InvalidFile;
- { Store frame offsets for later consumption }
- for Idx := 0 to fHeader.dwFrames - 1 do begin
- if ReadLong <> sig_icon then InvalidFile;
- { Save position from beginning of length dword }
- FrameOffsets.Add (Pointer (Image.Position));
- { Read Length of this frame }
- Len := ReadLong;
- Next := Len + Image.Position;
- { Dig a little deeper to get the icon size info }
- if Idx = 0 then begin
- Image.Position := Image.Position + 6;
- fHeader.dwCX := ReadByte;
- fHeader.dwCY := ReadByte;
- end;
-
- Image.Position := Next;
- end;
- end;
-
- begin { LoadFromStream }
- Clear;
- { If stream size is 0, we're done }
- if Stream.Size = 0 then Exit;
- Image.LoadFromStream (Stream);
- EncounteredHeader := False;
- { Validate initial eight-byte header }
- { Note: Some .ANI files have filesize > header (e.g. appstart.ani) }
- if (ReadLong <> sig_RIFF) or (ReadLong > Image.Size) then InvalidFile;
- { Next item must be an ACON chunk }
- if ReadLong <> sig_ACON then InvalidFile;
-
- while Image.Position < Image.Size do
- { Case out on the sub-chunk we find }
- case ReadLong of
- sig_LIST: if not EncounteredHeader then ParseTitleAuthor else ParseIconList;
- sig_anih: ParseAniHeader;
- sig_rate: ParseList (Rates);
- sig_seq: ParseList (SequenceMap);
-
- else begin { Unknown chunk - just skip it }
- ChunkLen := ReadLong;
- Image.Position := Image.Position + ChunkLen;
- end;
- end;
-
- SetFrame (0);
- end;
-
- procedure TAniIcon.SaveToStream (Stream: TStream);
- begin
- if GetEmpty then raise EInvalidGraphicOperation.Create (sInvalidImage);
- with Image do Stream.WriteBuffer (Memory^, Size);
- end;
-
- procedure TAniIcon.Draw (ACanvas: TCanvas; const Rect: TRect);
- var
- bm: TBitmap;
- begin
- if fCurrentIcon <> 0 then begin
- if not fTransparent then begin
- bm := TBitmap.Create;
- bm.Width := fHeader.dwCX;
- bm.Height := fHeader.dwCY;
- bm.Canvas.Brush.Color := fBackColor;
- bm.Canvas.FillRect (Classes.Rect (0, 0, bm.Width, bm.Height));
- DrawIcon (bm.Canvas.Handle, 0, 0, fCurrentIcon);
- ACanvas.Draw (Rect.Left, Rect.Top, bm);
- bm.Free;
- end else DrawIcon (ACanvas.Handle, Rect.Left, Rect.Top, fCurrentIcon);
- end;
- end;
-
- procedure TAniIcon.SetFrame (Index: Integer);
- type
- TIconHeader = packed record
- AlwaysZero: Word;
- CursorType: Word;
- NumIcons: Word;
- end;
-
- TIconDirEntry = packed record
- Width, Height, Colors: Byte;
- Reserved: Byte;
- dwReserved: LongInt;
- dwBytesInRes: LongInt;
- dwImageOffset: LongInt;
- end;
-
- var
- p: PByte;
- ChunkLen: LongInt;
- IconHeader: TIconHeader;
- begin
- if (FrameOffsets.Count <> 0) and (Index < fHeader.dwFrames) then begin
- fCurrentFrame := Index;
- // Delete any existing icon
- if fCurrentIcon <> 0 then DestroyIcon (fCurrentIcon);
- // Seek to wanted position in stream data
- Image.Position := Integer (FrameOffsets [Index]);
- Image.ReadBuffer (ChunkLen, sizeof (ChunkLen));
- Image.ReadBuffer (IconHeader, sizeof (IconHeader));
- Image.Position := Image.Position + (sizeof (TIconDirEntry) * IconHeader.NumIcons);
- Dec (ChunkLen, sizeof (IconHeader) + (sizeof (TIconDirEntry) * IconHeader.NumIcons));
-
- p := Image.Memory; Inc (p, Image.Position);
- fCurrentIcon := CreateIconFromResource (p, ChunkLen, True, $30000);
- Changed (Self);
- end;
- end;
-
- procedure TAniIcon.Animate;
- var
- JifRate, NextFrame: Integer;
- begin
- if Rates.Count = 0 then JifRate := fHeader.dwJIFRate else JifRate := Integer (Rates [fCurrentStep]);
- Inc (fCurrentJIFs, 4);
- if fCurrentJIFs >= JifRate then begin
- { Time to move on to next step }
- fCurrentJIFs := 0;
- Inc (fCurrentStep);
- if fCurrentStep >= fHeader.dwSteps then fCurrentStep := 0;
- if SequenceMap.Count = 0 then NextFrame := fCurrentFrame + 1 else NextFrame := Integer (SequenceMap [fCurrentStep]);
- if NextFrame >= fHeader.dwFrames then NextFrame := 0;
- if NextFrame <> fCurrentFrame then SetFrame (NextFrame);
- end;
- end;
-
- procedure TAniIcon.SetAnimatedCursor (Index: Integer);
- var
- TempFileName: String;
- begin
- if not Empty then begin
- TempFileName := FormatDateTime ('__$$hhnnss$$__', Now);
- SaveToFile (TempFileName);
- try
- Screen.Cursors [Index] := LoadImage (0, PChar (TempFileName), Image_Cursor, 0, 0, lr_LoadFromFile);
- finally
- DeleteFile (TempFileName);
- end;
- end;
- end;
-
- end.
-